package com.wusuowei.miniouploadfile.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.wusuowei.miniouploadfile.mapper.FilesMapper;
import com.wusuowei.miniouploadfile.model.po.Files;
import com.wusuowei.miniouploadfile.service.DownloadService;
import io.minio.*;
import io.minio.errors.ErrorResponseException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Date;

@Slf4j
@Service
public class DownloadServiceImpl implements DownloadService {


    @Resource
    private FilesMapper filesMapper;

    @Resource
    private MinioClient minioClient;


    @Override
    public void download(String fileId, HttpServletRequest request, HttpServletResponse response) {
        if (StringUtils.isBlank(fileId)) {
            log.info("文件id为空");
            throw new RuntimeException("文件id为空");
        }
        Files file = filesMapper.selectOne(new LambdaQueryWrapper<Files>().eq(Files::getId, fileId));
        if (file == null) {
            log.info("文件不存在，{}", fileId);
            throw new RuntimeException("文件不存在");
        }

        // 处理分片下载
        String bucketName = file.getBucketName();
        Long fileSize = file.getFileSize();
        String fileName = file.getFileName();
        try {
            StatObjectResponse statObjectResponse = minioClient.statObject(StatObjectArgs.builder()
                    .bucket(bucketName)
                    .object(fileName)
                    .build());
            fileSize = statObjectResponse.size();
        } catch (Exception e) {
            e.printStackTrace();
        }
        String range = request.getHeader(HttpHeaders.RANGE);

        // 请求的开始字节和结束字节
        long requestStart = 0, requestEnd = 0;
        if (range != null && range.startsWith("bytes=")) {
            String[] values = range.split("=")[1].split("-");
            if (StringUtils.isNotBlank(values[0])) {
                requestStart = Integer.parseInt(values[0]);
            }
            if (values.length > 1) {
                requestEnd = Integer.parseInt(values[1]);
            }
        }

        // 计算需要下载的数据流大小
        long requestSize;
        if (requestEnd > 0 && requestEnd > requestStart) {
            requestSize = requestEnd - requestStart + 1;
        } else {
            requestSize = fileSize;
        }
        response.setContentType("application/octet-stream");
        response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
        response.setHeader(HttpHeaders.ETAG, fileName);
        response.setHeader(HttpHeaders.LAST_MODIFIED, new Date().toString());

        try {
            response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(fileName, "UTF-8") + "\"");
        } catch (UnsupportedEncodingException e) {
            log.info("文件名设置失败！");
            e.printStackTrace();
        }

        //第一次请求只返回content length来让客户端请求多次实际数据
        if (range == null) {
            response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileSize));
        } else {
            //以后的多次以断点续传的方式来返回数据
            //set status 206
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            long length;
            if (requestEnd > 0) {
                length = requestEnd - requestStart + 1;
                response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(length));
                response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + requestStart + "-" + requestEnd + "/" + fileSize);
            } else {
                length = fileSize - requestStart;
                response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(length));
                response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + requestStart + "-" + (fileSize - 1) + "/" + fileSize);
            }
        }
        String fileMd5 = file.getFileMd5();
        response.setHeader("md5", fileMd5);
        partDownLoad(response, file, bucketName, requestStart, requestSize);
    }


    private void partDownLoad(HttpServletResponse response, Files file, String minIOBucketName, long requestStart, long requestSize) {
        GetObjectResponse minioResponse = null;
        ServletOutputStream out = null;
        try {
            minioResponse = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(minIOBucketName)
                    .object(file.getFileName())
                    .offset(requestStart)
                    .length(requestSize).build());

            out = response.getOutputStream();
            copyBytes(minioResponse, out);
            response.flushBuffer();
        } catch (ErrorResponseException e) {
            if (e.errorResponse() != null) {
                log.error("文件下载失败，文件id：{}，桶：{}，文件路径：{}，code：{}，message：{}",
                        file.getId(), file.getBucketName(), file.getFileName(), e.errorResponse().code(), e.errorResponse().message());
            } else {
                log.error("文件下载失败，文件id：{}，桶：{}，文件路径：{}", file.getId(), file.getBucketName(), file.getFileName(), e);
            }
        } catch (Exception e) {
            log.error("文件下载失败", e);
        } finally {
            try {
                if (minioResponse != null) {
                    minioResponse.close();
                }
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                log.error("关闭文件流异常", e);
            }
        }
    }


    private void copyBytes(InputStream in, OutputStream out) throws IOException {
        byte[] bytes = new byte[1024];
        int len;
        while ((len = in.read(bytes)) != -1) {
            out.write(bytes, 0, len);
        }
    }


}
